package org.goldfish.chatroom.intercepter;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.apache.ibatis.plugin.Intercepts;
import org.apache.logging.log4j.Logger;
import org.goldfish.chatroom.entity.VerificationMessage;
import org.goldfish.chatroom.mapper.MessageMapper;
import org.goldfish.chatroom.mapper.VerificationMessageMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import java.time.LocalDateTime;
import java.time.ZoneOffset;

@Component
@Intercepts({
        @org.apache.ibatis.plugin.Signature(
                type = Object.class,
                method = "methodName",
                args = {
                        HttpServletRequest.class,
                        HttpServletResponse.class,
                        Object.class
                }
        )
})
public class LogInterceptor implements HandlerInterceptor, Ordered {

    /**
     * allowed extensions<br>
     * it is strongly recommended to only use .* routes on static resources
     */
//    private final String[] ALLOWED_EXTENSIONS = {".css", ".js", ".png"};
    private static final Logger logger = org.apache.logging.log4j.LogManager.getLogger(LogInterceptor.class);


    @Autowired
    private MessageMapper messageMapper;
    @Autowired
    private VerificationMessageMapper verificationMessageMapper;

    public LogInterceptor() {
        super();
//        String[] prefixes = {"/", "/chatroom/"};
//        List<String> ALLOWED_PATHS = new ArrayList<>();
//        for (String prefix : prefixes) {
//            ALLOWED_PATHS.add(prefix);
//            ALLOWED_PATHS.add(prefix + "error");
//            ALLOWED_PATHS.add(prefix + "favicon.ico");
//        }
    }

    @Override
    public int getOrder() {
        return 1;
    }

    public boolean preHandle(
            @NonNull
            HttpServletRequest request,
            @NonNull
            HttpServletResponse response,
            @NonNull
            Object handler) throws Exception {
        long startTime = LocalDateTime.now().toEpochSecond(ZoneOffset.ofHours(8));
        VerificationMessage verificationMessage = verificationMessageMapper.getLatestVerificationMessage(false);
        String serialNumber = Long.toString(verificationMessage.getSerialNumber());
        logger.info(">>>>");
        // store where the sender is (i.e. ip or domain) in the request
        displayRequestInfo(request, true);
        request.setAttribute("name", request.getRemoteAddr());
        logger.info(">>>>");
        request.setAttribute("startTimeSTR", LocalDateTime.ofEpochSecond(startTime, 0, ZoneOffset.ofHours(8)));
        request.setAttribute("startTime", startTime);
        // if the request is for a path that is allowed, let it through
        // if the request is to / with a param logout with value true, unregister and let it through
        if (request.getRequestURI().endsWith("/") &&
                Boolean.parseBoolean(request.getParameter("logout"))
        ) {
            request.getSession().removeAttribute("sms");
            return true;
        }
        String reqNumber = String.valueOf(request.getParameter("sms"));

        if (serialNumber.equals(reqNumber)) {
            logger.info("IN => {} verified against {}", reqNumber, serialNumber);
            request.getSession().setAttribute("sms", serialNumber);
        } else
            logger.info("IN => {} not verified against {}. Not putting in session", reqNumber, serialNumber);
        return true;
    }


    private static void displayRequestInfo(HttpServletRequest request, Boolean preHandle) {
        String arrow = (preHandle == null) ? "=END=" : (preHandle ? "IN =>" : "OUT<=");
        logger.info("{} Remote Addr: {}", arrow, request.getRemoteAddr());
        logger.info("{} Remote Host: {}", arrow, request.getRemoteHost());

        logger.info("{} Method     : {}", arrow, request.getMethod());
        logger.info("{} Request URL: {}", arrow, request.getRequestURL());
        logger.info("{}         URI: {}", arrow, request.getRequestURI());
        logger.info("{} Start Time : {}", arrow, System.currentTimeMillis());
        //attributes
        logger.info("{} Attributes: ", arrow);
        request.getAttributeNames().asIterator().forEachRemaining(
                attribute -> {
                    if (!attribute.contains("org.springframework") && !attribute.contains("FILTERED"))
                        logger.info(" {} - {}: {}", arrow, attribute, request.getAttribute(attribute));
                }
        );
    }


    public void postHandle(
            @NonNull
            HttpServletRequest request,
            @NonNull
            HttpServletResponse response,
            @NonNull
            Object handler,
            ModelAndView modelAndView) {
        logger.info("--****--");

        Object sms;
        VerificationMessage verificationMessage = verificationMessageMapper.getLatestVerificationMessage(false);
        String serialNumber = Long.toString(verificationMessage.getSerialNumber());
        sms = request.getSession().getAttribute("sms");
        logger.info("<= verifying stored sms: {} against sms {}", sms, serialNumber);
        DECIDE_REDRAW:
        {
            String uri = request.getRequestURI();
            if (!uri.contains("/info")
                    &&
                    !uri.contains("/sealed")
            )
                // public sites
                break DECIDE_REDRAW;
            logger.info("protected site:{}", uri);
            if (sms != null && serialNumber.equals(sms.toString()))
                // verification success
                break DECIDE_REDRAW;
            logger.info("IN => {} not verified against {}. Redrawing view", sms, serialNumber);

            // unauthorized: re-render view
            modelAndView.setViewName("index");
            modelAndView.addObject("messages", messageMapper.getMessages());
            modelAndView.addObject("password_prompt", "Invalid SMS");
            request.removeAttribute("networkServices");
        }
        displayRequestInfo(request, false);
        logger.info("--****--");
    }

    public void afterCompletion(
            HttpServletRequest request,
            @NonNull
            HttpServletResponse response,
            @NonNull
            Object handler,
            @Nullable
            Exception exception
    ) {

        long startTime = request.getAttribute("startTime") == null ? 0L : (Long) request.getAttribute("startTime");
        long endTime = LocalDateTime.now().toEpochSecond(ZoneOffset.ofHours(8));
        request.setAttribute("endTimeSTR", LocalDateTime.now());

        logger.info(">>>>");
        displayRequestInfo(request, null);
        logger.info(" * End Time: {}", endTime);
        logger.info(" * Time Taken: {}", (endTime - startTime));
        logger.info(">>>>");
    }
}